package ui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.Timer;
import physics.EnvironmentSingleton;
import physics.EnvironmentSingleton.EnvironmentalForce;
import physics.Simulation;
import physics.globalforce.Wall;


/**
 * Creates an component that is a viewer onto an animation.
 *
 * @author Robert C. Duvall, slightly modified by Kevin Gao and Connor Gordon
 */
@SuppressWarnings("serial")
public class Canvas extends JComponent {
    /**
     * Return value when no key is pressed.
     */
    public static final int NO_KEY_PRESSED = -1;
    // animate 25 times per second if possible (in milliseconds)
    /**
     * Number of milliseconds in a second.
     */
    public static final int ONE_SECOND = 1000;
    /**
     * Defines the framerate for animation.
     */
    public static final int FRAMES_PER_SECOND = 25;
    // only one so that it maintains user's preferences
    private final JFileChooser myChooser = new JFileChooser(
            System.getProperties().getProperty("user.dir"));

    // user's game to be animated
    private final Simulation myTarget;
    // drives simulation
    private final Timer myTimer;
    // input state
    private int myLastKeyPressed;
    private Point myLastMousePosition;

    private final EnvironmentSingleton myEnvironment =
            EnvironmentSingleton.getInstance();

    /**
     * Initializes the canvas.
     *
     * @param size dimensions to make canvas
     */
    public Canvas (Dimension size) {
        // request component size
        setPreferredSize(size);
        // set component to receive user input
        setInputListeners();
        setFocusable(true);
        requestFocus();
        // create timer to drive the animation
        myTimer = new Timer(ONE_SECOND / FRAMES_PER_SECOND,
                new ActionListener() {
                    @Override
                    public void actionPerformed (ActionEvent e) {
                        step((double) FRAMES_PER_SECOND / ONE_SECOND);
                    }
                });
        // initialize simulation
        myTarget = new Simulation(this);
        loadModel();
        loadEnvironment();
    }

    private void loadEnvironment () {
        int n = JOptionPane.showConfirmDialog(
            this,
            "Would you like to load an environment different than the default?",
            "Choose your Environment", JOptionPane.YES_NO_OPTION);
        final Factory factory = new Factory();
        if (n == 0) {
            int response = myChooser.showOpenDialog(null);
            if (response == JFileChooser.APPROVE_OPTION) {
                factory.loadGlobalForces(
                        myTarget, myChooser.getSelectedFile());
            }
        }
        else {
            factory.loadGlobalForces(myTarget);
        }
    }

    /**
     * Starts the applet's action, i.e., starts the animation.
     */
    public void start () {
        myTimer.start();
    }

    /**
     * Take one step in the animation.
     *
     * @param elapsedTime The normalized time since last step
     */
    public void step (double elapsedTime) {
        myTarget.update(elapsedTime);
        // indirectly causes paint to be called
        repaint();
    }

    /**
     * Stops the applet's action, i.e., the animation.
     */
    public void stop () {
        myTimer.stop();
    }

    /**
     * Returns the last key pressed by the player (or -1 if none pressed).
     *
     * @see java.awt.event.KeyEvent
     * @return keyCode for last key pressed
     */
    public int getLastKeyPressed () {
        return myLastKeyPressed;
    }

    /**
     * Returns the last position of the mouse in the canvas.
     *
     * @return Point corresponding to the last mouse position
     */
    public Point getLastMousePosition () {
        return myLastMousePosition;
    }

    /**
     * Paint the contents of the canvas.
     *
     * Never called by you directly, instead called by Java runtime when area of
     * screen covered by this container needs to be displayed (i.e., creation,
     * uncovering, change in status)
     *
     * @param pen
     *        used to paint shape on the screen
     */
    @Override
    public void paintComponent (Graphics pen) {
        pen.setColor(Color.WHITE);
        pen.fillRect(0, 0, getSize().width, getSize().height);
        myTarget.paint((Graphics2D) pen);
    }

    /**
     * Create listeners that will update state based on user input.
     */
    private void setInputListeners () {
        myLastKeyPressed = NO_KEY_PRESSED;
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed (KeyEvent e) {
                myLastKeyPressed = e.getKeyCode();
                manageSimulation(myLastKeyPressed);
            }

            @Override
            public void keyReleased (KeyEvent e) {
                myLastKeyPressed = NO_KEY_PRESSED;
            }
        });

        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed (MouseEvent e) {
                myTarget.addUserMouseMass(e.getX(), e.getY());
            }

            @Override
            public void mouseReleased (MouseEvent e) {
                myTarget.removeUserMouseMass();
            }
        });

        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged (MouseEvent e) {
                myTarget.getUserMouseMass().setCenter(e.getX(), e.getY());
            }
        });
    }

    private void loadModel () {
        final Factory factory = new Factory();
        int response = myChooser.showOpenDialog(null);
        if (response == JFileChooser.APPROVE_OPTION) {
            factory.loadModel(myTarget, myChooser.getSelectedFile());
        }
    }

    private void manageSimulation (int keyCode) {
        switch (keyCode) {
            case KeyEvent.VK_SPACE:
                if (myTimer.isRunning()) {
                    stop();
                }
                else {
                    start();
                }
                break;
            case KeyEvent.VK_S:
                step((double) FRAMES_PER_SECOND / ONE_SECOND);
                break;
            case KeyEvent.VK_P:
                System.out.println(myTarget);
                break;
            case KeyEvent.VK_C:
                myTarget.clear();
                break;
            case KeyEvent.VK_N:
                loadModel();
                break;
            case KeyEvent.VK_E:
                loadEnvironment();
                break;
            case KeyEvent.VK_G:
                myEnvironment.toggleForceState(EnvironmentalForce.GRAVITY);
                break;
            case KeyEvent.VK_V:
                myEnvironment.toggleForceState(EnvironmentalForce.VISCOSITY);
                break;
            case KeyEvent.VK_M:
                myEnvironment.toggleForceState(
                        EnvironmentalForce.CENTERMASS);
                break;
            case KeyEvent.VK_W:
                myEnvironment.toggleForceState(EnvironmentalForce.WALL);
                break;
            case KeyEvent.VK_UP:
                Wall.increaseArea();
                break;
            case KeyEvent.VK_DOWN:
                Wall.decreaseArea();
                break;
            case KeyEvent.VK_1:
                Wall.toggleWall(Wall.TOP_WALL);
                break;
            case KeyEvent.VK_2:
                Wall.toggleWall(Wall.RIGHT_WALL);
                break;
            case KeyEvent.VK_3:
                Wall.toggleWall(Wall.BOTTOM_WALL);
                break;
            case KeyEvent.VK_4:
                Wall.toggleWall(Wall.LEFT_WALL);
                break;
            default:
                // good style
                break;
        }
    }
}
